#include "grflib_stdafx.h"
#include "../include/Texture.h"
#include "../include/D3D10Engine.h"
#include "../include/DXUTBindings.h"
#include "../include/ITraverse.h"
#include "../include/MsgRouter.h"
#include "../include/GUIEventDispatcher.h"
#include <boost/bind.hpp>
#include "../include/MousePicker.h"
#include "../include/PointerCast.h"

using namespace grflib::engine::core;
using namespace grflib::engine::aux;

#define LPGUIEVENTMANAGER CGUIEventManager::Instance()

CEngine::CEngine()
    : m_pd3dDevice(0),
    m_pCurRenderTargetView(0),
    m_pCurDepthStencilView(0),
    m_pBackBufferRTV(0),
    m_pBackBufferDSV(0),
    m_nPass(1),
    m_bRenderingToBackBuffer(true)
{
    memset(m_clearColor, 0, sizeof(float) * 4);
}

CEngine::~CEngine()
{
    DefaultOnDestroy(0);
    m_searchPaths.Clear();
}

CEngine &CEngine::Instance()
{
    static CEngine engine;
    return engine;
}

int CEngine::Run(HWND hWnd, UINT width, UINT height, bool bShowCursor)
{
    //Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

    //Clear last-run residuals
    m_resourceManager.Clear();
    m_guiManager.Clear();
    m_renderableManager.Clear();
    _GUIDispatcher->Clear();
    m_searchPaths.Clear();
    m_sceneManager.Clear();
    m_cameraManager.Clear();
    
    m_IAStage.OnDestroy();
    
    //Set default callbacks
    DXUTSetCallbackD3D10DeviceAcceptable(D3D10::IsD3D10DeviceAcceptable);
    DXUTSetCallbackD3D10DeviceCreated(D3D10::OnD3D10CreateDevice);
    DXUTSetCallbackD3D10SwapChainResized(D3D10::OnD3D10ResizedSwapChain);
    DXUTSetCallbackD3D10SwapChainReleasing(D3D10::OnD3D10ReleasingSwapChain);
    DXUTSetCallbackD3D10DeviceDestroyed(D3D10::OnD3D10DestroyDevice);
    DXUTSetCallbackD3D10FrameRender(D3D10::OnD3D10FrameRender);
    DXUTSetCallbackMsgProc(D3D10::OnD3D10Msg);
    DXUTSetCallbackKeyboard(CGUIEventManager<>::OnKey);
    DXUTSetCallbackMouse(CGUIEventManager<>::OnMouse);
    DXUTSetCallbackFrameMove(D3D10::OnFrameMove);
    DXUTSetCallbackDeviceChanging(D3D10::ModifyDeviceSettings);

    //Create mouse picker
    boost::shared_ptr<CMousePicker> pMousePicker(new CMousePicker());
    if (!pMousePicker)
    {
        W32ERR(_T("Failed to create mouse picker!"));
        return 1;
    }
    CGUIEventManager<>::Instance()->AddObj("engine:mouse_picker", cast::IInteractiveCast(pMousePicker));

    
#if defined(DEBUG) || defined(_DEBUG)
#else
    m_profiler.Mute();
    m_profiler.Disable();
#endif

    //Init UI, scene, camera
    if (!m_initSceneCallback.empty())
        if (!m_initSceneCallback()) return 1;

    //Create window
    DXUTInit(false, true, NULL);
    DXUTSetCursorSettings(bShowCursor, bShowCursor);
    DXUTSetWindow(hWnd, hWnd, hWnd, false);
    DXUTCreateDevice(true, width, height);
    m_pd3dDevice = DXUTGetD3D10Device();
    ResetRenderTarget();
    //DXUTMainLoop();

    //return DXUTGetExitCode();
    return 0;
}

int CEngine::Run(TCHAR *wndName, UINT width, UINT height, bool bShowCursor)
{
    //Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

    //Set default callbacks
    DXUTSetCallbackD3D10DeviceAcceptable(D3D10::IsD3D10DeviceAcceptable);
    DXUTSetCallbackD3D10DeviceCreated(D3D10::OnD3D10CreateDevice);
    DXUTSetCallbackD3D10SwapChainResized(D3D10::OnD3D10ResizedSwapChain);
    DXUTSetCallbackD3D10SwapChainReleasing(D3D10::OnD3D10ReleasingSwapChain);
    DXUTSetCallbackD3D10DeviceDestroyed(D3D10::OnD3D10DestroyDevice);
    DXUTSetCallbackD3D10FrameRender(D3D10::OnD3D10FrameRender);
    DXUTSetCallbackMsgProc(D3D10::OnD3D10Msg);
    DXUTSetCallbackKeyboard(CGUIEventManager<>::OnKey);
    DXUTSetCallbackMouse(CGUIEventManager<>::OnMouse);
    DXUTSetCallbackFrameMove(D3D10::OnFrameMove);
    DXUTSetCallbackDeviceChanging(D3D10::ModifyDeviceSettings);

    //Create mouse picker
    boost::shared_ptr<CMousePicker> pMousePicker(new CMousePicker());
    if (!pMousePicker)
    {
        W32ERR(_T("Failed to create mouse picker!"));
        return 1;
    }
    CGUIEventManager<>::Instance()->AddObj("engine:mouse_picker", cast::IInteractiveCast(pMousePicker));

#if defined(DEBUG) || defined(_DEBUG)
#else
    m_profiler.Mute();
    m_profiler.Disable();
#endif

    //Init UI, scene, camera
    if (!m_initSceneCallback.empty())
        if (!m_initSceneCallback()) return 1;

    //Create window
    DXUTInit(false, true, NULL);
    DXUTSetCursorSettings(bShowCursor, bShowCursor);
    DXUTCreateWindow(wndName);
    DXUTCreateDevice(true, width, height);
    m_pd3dDevice = DXUTGetD3D10Device();
    ResetRenderTarget();
    DXUTMainLoop();

    return DXUTGetExitCode();
}

HRESULT CEngine::DefaultOnCreate(ID3D10Device* pd3dDevice, const DXGI_SURFACE_DESC* pBufferSurfaceDesc, void* pUserContext )
{
    HRESULT hr;
    
    m_pd3dDevice = pd3dDevice;

	if (!m_preCreateDeviceCallback.empty())
	{
		V_RETURN( m_preCreateDeviceCallback(pd3dDevice, pBufferSurfaceDesc, pUserContext) );
	}

    m_guiManager.OnCreateDevice(pd3dDevice);

    ICreateFunc<IResource> resrcCreator(pd3dDevice, pBufferSurfaceDesc);
    V_RETURN( m_resourceManager.Traverse(resrcCreator) );

    ICreateFunc<IRenderable> objCreator(pd3dDevice, pBufferSurfaceDesc);
    V_RETURN( m_renderableManager.Traverse(objCreator) );

    m_txtLabelManager.OnCreateDevice(pd3dDevice, pBufferSurfaceDesc);

    //call customized function
    if (!m_postCreateDeviceCallback.empty())
    {
        V_RETURN( m_postCreateDeviceCallback(pd3dDevice, pBufferSurfaceDesc, pUserContext) );
    }

    return S_OK;
}

HRESULT CEngine::DefaultOnResize(ID3D10Device* pd3dDevice, IDXGISwapChain* pSwapChain, const DXGI_SURFACE_DESC* pBufferSurfaceDesc, void* pUserContext)
{
    HRESULT hr;

    m_pBackBufferRTV = DXUTGetD3D10RenderTargetView();
    m_pBackBufferDSV = DXUTGetD3D10DepthStencilView();
    if (m_bRenderingToBackBuffer)
    {
        m_pCurRenderTargetView = m_pBackBufferRTV;
        m_pCurDepthStencilView = m_pBackBufferDSV;
    }
    
    m_cameraManager.OnResize(pd3dDevice, pSwapChain, pBufferSurfaceDesc);
    m_guiManager.OnResize(pd3dDevice, pSwapChain, pBufferSurfaceDesc);

    IResizeFunc<IRenderable> memberResizer(pd3dDevice, pSwapChain, pBufferSurfaceDesc);
    V_RETURN( m_renderableManager.Traverse(memberResizer) );

    //call customized resize functions
    if (!m_resizeCallback.empty())
    {
        V_RETURN( m_resizeCallback(pd3dDevice, pSwapChain, pBufferSurfaceDesc, pUserContext) );
    }

    return S_OK;
}

void CEngine::DefaultOnFrameMove(double fTime, float fElapsedTime, void* pUserContext)
{
    m_cameraManager.OnFrameMove(fTime, fElapsedTime); //move only active cameras

    //handle messages
    _MsgRouter.Dispatch();

    //update individual objects
    IFrameMoveFunc<CSceneObj> frameMover(fTime, fElapsedTime);
    m_sceneManager.TraverseSceneGraph(frameMover);

    IFrameMoveFunc<CTextLabel> txtFrameMover(fTime, fElapsedTime);
    m_txtLabelManager.Traverse(txtFrameMover);

    m_sceneManager.UpdateWorldCoord();

    //rebuild scene graph
    CSceneGraph<CSceneObj, CRenderQueue> *pSG = m_sceneManager.GetSceneGraphPtr();
    if (pSG)
        pSG->Update();

    //call customized frame move
    if (!m_frameMoveCallback.empty())
        m_frameMoveCallback(fTime, fElapsedTime, pUserContext);
}

HRESULT CEngine::DefaultRenderFrame(ID3D10Device* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext)
{
    HRESULT hr;

    //call customized render function if exists
    if (!m_renderCallback.empty())
        V_RETURN( m_renderCallback(pd3dDevice, fTime, fElapsedTime, pUserContext) );

    //temporary! for DEBUG ONLY
    m_renderQueue.clear();
    CBaseCamera *pCam = m_cameraManager.GetCurrentCamera()->GetDXUTCamera();
    CSceneGraph<CSceneObj, CRenderQueue> *pSG = m_sceneManager.GetSceneGraphPtr();
    if (pSG)
        pSG->GenerateRenderQueue(m_renderQueue, pCam->GetViewMatrix(), pCam->GetProjMatrix());

    //render multiple passes
    IRenderFunc<> renderer(pd3dDevice, 0, fTime, fElapsedTime, 0);
    for (UINT pass = 0; pass < m_nPass; pass++)
    {
        ClearTargetFlag clearTargetFlag = CLEAR_ALL;
        //invoke pre-pass callback
        if (!m_prePassCallback.empty())
            clearTargetFlag = m_prePassCallback(pass);

        //Clear buffers
        if (clearTargetFlag & CLEAR_RGBA)
            pd3dDevice->ClearRenderTargetView(m_pCurRenderTargetView, m_clearColor);
        if (clearTargetFlag & (CLEAR_DEPTH | CLEAR_STENCIL))
            pd3dDevice->ClearDepthStencilView(m_pCurDepthStencilView, 
                            (clearTargetFlag & CLEAR_DEPTH ? D3D10_CLEAR_DEPTH : 0)
                            | (clearTargetFlag & CLEAR_STENCIL ? D3D10_CLEAR_STENCIL : 0), 
                            1.0f, 0);

        //render the scene
        renderer.SetPass(pass);
        V_RETURN( TraverseRenderQueue(renderer, m_renderQueue) );
    }

    //call post-render processor
    if (!m_postRenderCallback.empty())
        V_RETURN( m_postRenderCallback(pd3dDevice, fTime, fElapsedTime, pUserContext) );

    //render UI
    IRenderFunc<CDialog> uiRenderer(pd3dDevice, 0, fTime, fElapsedTime, 0);
    V_RETURN( m_guiManager.Traverse(uiRenderer) );
    
    V_RETURN( m_txtLabelManager.OnRender(pd3dDevice, fTime, fElapsedTime) );


    return S_OK;
}

LRESULT CEngine::DefaultMsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing, void* pUserContext)
{
    LRESULT lResult = 0;
    
    //dialogs must handle messages first
    if (m_guiManager.MsgProc(hWnd, uMsg, wParam, lParam))
        return 0;

    lResult = m_cameraManager.HandleMessages(hWnd, uMsg, wParam, lParam);

    if (!m_msgProc.empty())
        lResult = m_msgProc(hWnd, uMsg, wParam, lParam, pbNoFurtherProcessing, pUserContext);
    return lResult;
}

void CEngine::DefaultOnRelease(void *pUserContext)
{
    m_guiManager.Release();
}

void CEngine::DefaultOnDestroy(void *pUserContext)
{
    m_guiManager.OnDestroy();
    
    IDestroyFunc<IRenderable> destroyer;
    m_renderableManager.Traverse(destroyer);

    IDestroyFunc<IResource> resrcDestroyer;
    m_resourceManager.Traverse(resrcDestroyer);

    m_txtLabelManager.OnDestroy();

    m_IAStage.OnDestroy();

    if (!m_destroyCallback.empty())
        m_destroyCallback(pUserContext);
}

HRESULT CEngine::SetRenderTarget(boost::shared_ptr<CTexture> &pRenderTargetTexture, boost::shared_ptr<CTexture> &pDepthStencilTexture)
{
    if (!m_pd3dDevice 
        || !pRenderTargetTexture || !pDepthStencilTexture)
    {
        W32ERR(_T("Invalid render target or depth stencil texture!"));
        return E_INVALIDARG;
    }

    ID3D10RenderTargetView *pRTV = pRenderTargetTexture->GetRenderTargetView();
    ID3D10DepthStencilView *pDSV = pDepthStencilTexture->GetDepthStencilView();

    if (!pRTV || !pDSV)
    {
        W32ERR(_T("Invalid render target or depth stencil resource view!"));
        return E_INVALIDARG;
    }

    m_pd3dDevice->OMSetRenderTargets(1, &pRTV, pDSV);
    m_pCurRenderTargetView = pRTV;
    m_pCurDepthStencilView = pDSV;
    
    CTexture2D *pt2d = dynamic_cast<CTexture2D *>(pRenderTargetTexture.get());
    CTexture1D *pt1d = dynamic_cast<CTexture1D *>(pRenderTargetTexture.get());
    CTextureCube *ptcube = dynamic_cast<CTextureCube *>(pRenderTargetTexture.get());

    D3D10_VIEWPORT vp;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;

    if (pt2d)
    {
        vp.Width = pt2d->GetWidth();
        vp.Height = pt2d->GetHeight();
    }
    else if (pt1d)
    {
        vp.Width = pt1d->GetLength();
        vp.Height = 1;
    }
    else if (ptcube)
    {
        vp.Width = ptcube->GetFaceWidth();
        vp.Height = ptcube->GetFaceHeight();
    }

    m_pd3dDevice->RSSetViewports(1, &vp);
    m_cameraManager.OnResize(vp.Width, vp.Height);

    m_bRenderingToBackBuffer = false;

    return S_OK;
}

HRESULT CEngine::SetRenderTarget(char *renderTargetName, char *depthStencilName)
{
    boost::shared_ptr<CTexture> pRT = boost::dynamic_pointer_cast<CTexture>(m_resourceManager.FindObj(renderTargetName));
    boost::shared_ptr<CTexture> pDS = boost::dynamic_pointer_cast<CTexture>(m_resourceManager.FindObj(depthStencilName));

    return SetRenderTarget(pRT, pDS);
}

void CEngine::ResetRenderTarget()
{
    ID3D10RenderTargetView *pRTV = DXUTGetD3D10RenderTargetView();
    ID3D10DepthStencilView *pDSV = DXUTGetD3D10DepthStencilView();
    m_pd3dDevice->OMSetRenderTargets(1, &pRTV, pDSV);
    m_pCurRenderTargetView = m_pBackBufferRTV = pRTV;
    m_pCurDepthStencilView = m_pBackBufferDSV = pDSV;
    m_bRenderingToBackBuffer = true;

    const DXGI_SURFACE_DESC *pBackSurfaceDesc = DXUTGetDXGIBackBufferSurfaceDesc();
    D3D10_VIEWPORT vp;
    vp.Width = pBackSurfaceDesc->Width;
    vp.Height = pBackSurfaceDesc->Height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    m_cameraManager.OnResize(vp.Width, vp.Height);
    m_pd3dDevice->RSSetViewports(1, &vp);
}

boost::shared_ptr<CMousePicker> CEngine::GetMousePicker()
{
    return boost::dynamic_pointer_cast<CMousePicker>(CGUIEventManager<>::Instance()->FindObj("engine:mouse_picker"));
}